package com.trailwalker.JavaUtils.XmlUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Trailwalker
 * @create 2020-10-19 1:30
 */
public class XmlParser {

    private static final String TAG = XmlParser.class.getSimpleName();

    private XmlParserCallback mXmlParserCallback;
    private String mStrXml;
    private char[] mXmlChars;
    private List<String> mainTagList;
    private int mainPointer;
    private List<String> branchTagList;
    private int branchPointer;
    private boolean mIsSuccess;
    private boolean mIsError;
    private String errorReason;

    public XmlParser(String strXml, XmlParserCallback xmlParserCallback) {
        mainTagList = new ArrayList<>();
        branchTagList = new ArrayList<>();
        mStrXml = strXml;
        mXmlParserCallback = xmlParserCallback;
    }

    public XmlParser() {
        mainTagList = new ArrayList<>();
        branchTagList = new ArrayList<>();
    }

    public void setXmlParserCallback(XmlParserCallback xmlParserCallback) {
        mXmlParserCallback = xmlParserCallback;
    }

    public void setStrXml(String strXml) {
        mStrXml = strXml;
    }

    public void parse() {
        if (mXmlParserCallback == null) {
            return;
        }
        if (mStrXml == null || mStrXml.length() == 0) {
            mIsError = true;
            errorReason = "no xml string";
            mXmlParserCallback.error(errorReason);
            return;
        }

        StringBuilder sbStrXml = new StringBuilder();
        sbStrXml.append("<Trailwalker>");
        sbStrXml.append(mStrXml);
        sbStrXml.append("</Trailwalker>");

        mXmlChars = sbStrXml.toString().toCharArray();

        mainPointer = 0;
        mIsSuccess = false;
        mIsError = false;

        String startDocument = getStartDocument();
        mXmlParserCallback.startDocument(startDocument);
        mainTagList.add(startDocument);
        branchTagList.add(startDocument);
        String document = getDocument();
        mXmlParserCallback.document(document);
        String endDocument = getEndDocument();
        mXmlParserCallback.endDocument(endDocument);

        while (!mIsSuccess && !mIsError) {
            String startElement = getStartElement();
            List<Attribute> attributeList = null;
            if (startElement != null) {
                mainTagList.add(startElement);
                branchTagList.add(startElement);
                attributeList = getAttributeList(startElement.toCharArray());
                mXmlParserCallback.startElement(startElement, attributeList);
            }
            if (!mIsSuccess && !mIsError) {
                String content = getContent();
                mXmlParserCallback.content(content);
            }
            if (!mIsSuccess && !mIsError) {
                String endElement = getEndElement();
                mXmlParserCallback.endElement(endElement);
            }
        }

        if (mIsSuccess) {
            mXmlParserCallback.success();
        }
    }

    /**
     * 获取标签的属性列表
     *
     * @param startElement
     */
    private List<Attribute> getAttributeList(char[] startElement) {
        ArrayList<Attribute> attributeList = new ArrayList<>();
        int pointer = 0;
        while (pointer < startElement.length && startElement[pointer] != ' ') {
            pointer++;
        }

        while (pointer < startElement.length && startElement[pointer] == ' ') {
            pointer++;
        }

        while (pointer < startElement.length - 1) {
            Attribute attribute = new Attribute();
            StringBuilder key = new StringBuilder();
            while (startElement[pointer] != '=') {
                key.append(startElement[pointer]);
                pointer++;
            }
            while (startElement[pointer] != '"') {
                pointer++;
            }
            pointer++;
            StringBuilder value = new StringBuilder();
            while (startElement[pointer] != '"') {
                value.append(startElement[pointer]);
                pointer++;
            }
            pointer++;
            attribute.setKey(key.toString().trim());
            attribute.setValue(value.toString().trim());
            attributeList.add(attribute);

            while (startElement[pointer] == ' ') {
                pointer++;
            }
        }

        return attributeList;
    }

    /**
     * 获取结束标签
     */
    private String getEndElement() {
        final String[] endDocument = {null};
        if (branchPointer < mXmlChars.length) {
            getTag(branchPointer, mXmlChars, new TagCallback() {
                @Override
                public void callback(boolean isSuccess, int callbackStartPointer, String tag) {
                    if (isSuccess) {
                        branchPointer = callbackStartPointer + tag.length();
                        endDocument[0] = tag;
                    }
                }
            });
        }
        return endDocument[0];
    }

    /**
     * 获取文档结束标签
     *
     * @return
     */
    private String getEndDocument() {
        if (branchPointer >= mXmlChars.length) {
            return "</>";
        } else {
            final String[] endDocument = {"</>"};
            getTag(branchPointer, mXmlChars, new TagCallback() {
                @Override
                public void callback(boolean isSuccess, int callbackStartPointer, String tag) {
                    if (isSuccess) {
                        branchPointer = callbackStartPointer + tag.length();
                        endDocument[0] = tag;
                    }
                }
            });
            return endDocument[0];
        }
    }

    /**
     * 获取开始标签
     *
     * @return
     */
    private String getStartElement() {
        branchTagList.clear();
        branchPointer = 0;
        final String[] startTag = {null};
        do {
            getTag(mainPointer, mXmlChars, new TagCallback() {
                @Override
                public void callback(boolean isSuccess, int callbackStartPointer, String tag) {
                    if (isSuccess) {
                        mainPointer = callbackStartPointer + tag.length();
                        branchPointer = callbackStartPointer + tag.length();
                        startTag[0] = tag;
                    } else {
                        //已经拿不到开始标签了
                        mIsSuccess = true;
                    }
                }
            });
        } while ((startTag[0] != null && isEndTag(startTag[0])) && !mIsSuccess);
        if (mIsSuccess) {
            startTag[0] = null;
        }
        return startTag[0];
    }

    private String getStartDocument() {
        if (isStartWithTag(mXmlChars)) {
            final String[] tagElement = new String[1];
            getTag(mainPointer, mXmlChars, new TagCallback() {
                @Override
                public void callback(boolean isSuccess, int callbackStartPointer, String tag) {
                    if (isSuccess) {
                        branchPointer = callbackStartPointer + tag.length();
                        tagElement[0] = tag;
                    }
                }
            });
            return tagElement[0];
        } else {
            return "<>";
        }
    }

    /**
     * 获取标签的内容
     *
     * @return
     */
    private String getContent() {
        StringBuilder content = new StringBuilder();
        while (branchPointer < mXmlChars.length) {
            char xmlChar = mXmlChars[branchPointer];
            if (xmlChar == '<') {
                //可能解析到标签了
                final boolean[] isParseSuccess = {false};
                getTag(branchPointer, mXmlChars, new TagCallback() {
                    @Override
                    public void callback(boolean isSuccess, int callbackStartPointer, String tag) {
                        if (isSuccess) {
                            while (branchPointer < callbackStartPointer) {
                                //标签之前的字符拿出来
                                content.append(mXmlChars[branchPointer]);
                                branchPointer++;
                            }
                            if (isStartTag(tag)) {
                                //开始标签
                                branchTagList.add(tag);
                            } else {
                                //结束标签
                                branchTagList.remove(branchTagList.size() - 1);
                                if (branchTagList.size() == 0) {
                                    //分支标签列表清空了
                                    //读取成功
                                    isParseSuccess[0] = true;
                                    return;
                                }
                            }
                            while (branchPointer < callbackStartPointer + tag.length()) {
                                //标签的字符拿出来
                                content.append(mXmlChars[branchPointer]);
                                branchPointer++;
                            }
                        }
                    }
                });
                if (isParseSuccess[0]) {
                    //读取成功
                    return content.toString();
                }
            } else {
                content.append(xmlChar);
                branchPointer++;
            }
        }
        //没有从循环中返回出去，说明没有找到对应的结束标签
        //读取失败
        mIsError = true;
        mXmlParserCallback.error("no end tag");
        return content.toString();
    }

    /**
     * 获取文档的内容
     *
     * @return
     */
    private String getDocument() {
        StringBuilder document = new StringBuilder();
        while (branchPointer < mXmlChars.length) {
            char xmlChar = mXmlChars[branchPointer];
            if (xmlChar == '<') {
                final boolean[] isParseSuccess = {false};
                getTag(branchPointer, mXmlChars, new TagCallback() {
                    @Override
                    public void callback(boolean isSuccess, int callbackStartPointer, String tag) {
                        if (isSuccess) {
                            while (branchPointer < callbackStartPointer) {
                                //标签之前的字符拿出来
                                document.append(mXmlChars[branchPointer]);
                                branchPointer++;
                            }
                            if (isStartTag(tag)) {
                                //开始标签
                                branchTagList.add(tag);
                            } else {
                                //结束标签
                                branchTagList.remove(branchTagList.size() - 1);
                                if (branchPointer + tag.length() == mXmlChars.length) {
                                    //读取完了
                                    if (branchTagList.size() == 0 || (branchTagList.size() == 1 && branchTagList.get(0).equals(""))) {
                                        //读取成功
                                        isParseSuccess[0] = true;
                                        return;
                                    } else {
                                        //读取失败
                                        mIsError = true;
                                        mXmlParserCallback.error("parse error");
                                    }
                                }
                            }
                            while (branchPointer < callbackStartPointer + tag.length()) {
                                //标签的字符拿出来
                                document.append(mXmlChars[branchPointer]);
                                branchPointer++;
                            }
                        }
                    }
                });
                if (isParseSuccess[0]) {
                    //读取成功
                    return document.toString();
                }
            } else {
                document.append(xmlChar);
                branchPointer++;
            }
        }
        if (branchTagList.size() == 0 || (branchTagList.size() == 1 && branchTagList.get(0).equals(""))) {
            //读取成功
            return document.toString();
        } else {
            //读取失败
            mIsError = true;
            mXmlParserCallback.error("parse error");
            return document.toString();
        }
    }

    /**
     * 是否为结束标签
     *
     * @param tag
     * @return
     */
    private boolean isEndTag(String tag) {
        if (!isTag(tag)) {
            return false;
        } else {
            if (tag.startsWith("</")) {
                return true;
            } else {
                return false;
            }
        }
    }

    /**
     * 是否为开始标签
     *
     * @param tag
     * @return
     */
    private boolean isStartTag(String tag) {
        if (!isTag(tag)) {
            return false;
        } else {
            if (isEndTag(tag)) {
                return false;
            } else {
                return true;
            }
        }
    }

    /**
     * 是否为标签
     *
     * @param tag
     * @return
     */
    private boolean isTag(String tag) {
        if (tag.startsWith("<") && tag.endsWith(">")) {
            if (tag.substring(1, tag.length() - 1).contains("<") || tag.substring(1, tag.length() - 1).contains(">")) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    }

    private boolean isStartWithTag(char[] xmlChars) {
        int pointer = 0;
        char xmlChar = xmlChars[pointer];
        pointer++;
        if (xmlChar != '<') {
            return false;
        } else {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("<");
            for (int i = pointer; i < xmlChars.length; i++) {
                if (xmlChars[pointer] == '>') {
                    stringBuilder.append('>');
                    pointer++;
                    break;
                } else {
                    stringBuilder.append(xmlChars[pointer]);
                    pointer++;
                }
            }
            String strMaybeTag = stringBuilder.toString();
            if (strMaybeTag.endsWith(">")) {
                if (strMaybeTag.substring(1).contains("<")) {
                    //说明第一个'<'并不是标签的开头
                    return false;
                } else {
                    return true;
                }
            } else {
                //说明没有找到'>'符号
                return false;
            }
        }
    }

    /**
     * 获取标签
     *
     * @param startPointer 开始指针位置
     * @param xmlChars     xml字节
     * @param tagCallback  标签回调
     */
    private void getTag(int startPointer, char[] xmlChars, TagCallback tagCallback) {
        int pointer = startPointer;
        int callbackStartPointer = startPointer;
        StringBuilder tag = new StringBuilder();
        for (int i = pointer; i < xmlChars.length; i++) {
            char xmlChar = xmlChars[pointer];
            if (xmlChar == '<') {
                //可能碰到标签开头了
                callbackStartPointer = pointer;
                tag.delete(0, tag.length());
                tag.append(xmlChar);
            } else {
                if (xmlChar == '>') {
                    if (tag.toString().contains("<")) {
                        tag.append(xmlChar);
                        tagCallback.callback(true, callbackStartPointer, tag.toString());
                        return;
                    }
                }
                tag.append(xmlChar);
            }
            pointer++;
        }
        tagCallback.callback(false, callbackStartPointer, null);
    }

    public class Attribute {
        private String key;
        private String value;

        public String getKey() {
            return key;
        }

        public void setKey(String key) {
            this.key = key;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

    private interface TagCallback {
        void callback(boolean isSuccess, int callbackStartPointer, String tag);
    }

    public interface XmlParserCallback {
        void startDocument(String startDocument);

        void document(String document);

        void endDocument(String endDocument);

        void startElement(String startElement, List<Attribute> attributeList);

        void endElement(String endDocument);

        void content(String content);

        void error(String error);

        void success();
    }
}
